contents
슈퍼 클래스, 추상 클래스, 인터페이스의 차이점을 정리해보겠습니다.
| 구분 | 슈퍼 클래스 (Super Class) | 추상 클래스 (Abstract Class) | 인터페이스 (Interface) |
|---|---|---|---|
| 개념 | 다른 클래스가 상속받는 일반 클래스 | 하나 이상의 추상 메서드를 포함할 수 있는 클래스 | 구현되지 않은 메서드(추상 메서드)만 가질 수 있는 일종의 규격 |
| 인스턴스 생성 가능 여부 | 가능 | 직접 인스턴스 생성 불가 (자식 클래스에서 구현 필요) | 직접 인스턴스 생성 불가 (구현 클래스 필요) |
| 메서드 구현 | 모든 메서드 구현 가능 | 일반 메서드와 추상 메서드 모두 포함 가능 | Java 8 이상부터는 default·static 메서드 구현 가능, 기본은 추상 메서드 |
| 변수 | 모든 변수 선언 가능 | 모든 변수 선언 가능 | 상수(public static final)만 선언 가능 |
| 상속 규칙 | 단일 상속 | 단일 상속 가능 | 다중 구현 가능 |
| 목적 | 공통 속성과 동작의 재사용 | 공통 기능 일부 구현과 하위 클래스에서의 확장 강제 | 클래스가 특정 역할을 수행하도록 메서드 구현 강제 |
| 사용 예 | 기능이 일부 구현된 일반 클래스 | 템플릿 메서드 패턴 등에 자주 사용 | 다중 행위를 대체하는 역할, 콜백, API 규약 정의 |
| 접근 제한자 | 자유롭게 사용 가능 | 자유롭게 사용 가능 | 모든 메서드는 기본적으로 public |
| 상속 관계 표현 | IS-A 관계 | IS-A 관계 | 역할(능력) 명세 |
간단 설명
- 슈퍼 클래스는 자식 클래스가 재사용할 공통된 행동과 상태를 가진 일반 클래스입니다.
- 추상 클래스는 일부 메서드가 구현되어 있지만, 최소 하나 이상의 추상 메서드를 포함하여 하위 클래스에서 반드시 구현하도록 강제하는 클래스입니다.
- 인터페이스는 구현 코드 없이 메서드의 형식만 선언하여, 여러 클래스가 동일한 행위를 구현하도록 하는 계약(규격)을 정의하는 역할입니다.
예시
// 슈퍼 클래스
class Animal {
void eat() { System.out.println("먹는다"); }
}
// 추상 클래스
abstract class Bird {
void fly() { System.out.println("날다"); }
abstract void sing();
}
// 인터페이스
interface Swimmable {
void swim();
}
// 구현 예
class Sparrow extends Bird {
void sing() { System.out.println("짹짹"); }
}
class Fish implements Swimmable {
public void swim() { System.out.println("헤엄치다"); }
}
각 개념(슈퍼 클래스, 추상 클래스, 인터페이스)의 장단점, 쓰임새, 그리고 객체지향 설계에서 선택 기준을 아래와 같이 정리합니다.
1. 슈퍼 클래스(Super Class, 일반 클래스 상속)
장점
- 코드 재사용: 공통 동작(메서드)과 공통 상태(필드)를 여러 하위 클래스에서 손쉽게 재사용할 수 있습니다.
- 확장성: 상속받은 자식 클래스에서 슈퍼 클래스의 기능을 추가·확장하거나 오버라이딩(overriding)해서 행동 변경이 가능합니다.
- 캡슐화: 중복된 코드를 상위 클래스로 몰아서 관리함으로써 코드의 일관성과 유지보수성을 높일 수 있습니다.
단점
- 강한 결합: 부모와 자식 클래스가 밀접하게 연결되어있어 상속 구조가 복잡해지면 유지보수가 어려워질 수 있습니다.
- 단일 상속 제한: Java는 단일 상속만 지원해 여러 타입의 성질을 동시에 물려받을 수 없습니다.
- 'is-a' 관계만 허용: 상속은 본질적으로 ‘~이다’라는 관계일 때만 적합하므로, 잘못 사용하면 잘못된 계층 구조가 만들어질 수 있습니다.
쓰임새
- 동물 계층, 차량 계층 등 계층화가 명확한 도메인에서 공통 동작 및 데이터를 상속할 때 사용합니다.
2. 추상 클래스(Abstract Class)
장점
- 기본 구현 제공: 일부 메서드를 미리 구현해둘 수 있어 하위 클래스의 개발 부담을 줄여줍니다.
- 템플릿 메서드 패턴: 전체 알고리즘(틀)은 상위에서 정의하고, 핵심 구현만 하위 클래스에 위임할 수 있습니다.
- 상태(필드)·생성자 가짐: 하위 객체의 공통 상태 및 생성자 로직을 손쉽게 정의할 수 있습니다.
단점
- 단일 상속: 일반 클래스와 동일하게 여러 클래스로부터는 상속받을 수 없습니다.
- 역할 분리 한계: 다양한 기능을 조합한 객체를 만들기엔 한계가 있습니다(즉, 다중 역할 구현엔 불리).
쓰임새
- 여러 하위 클래스가 '기본 행동'은 같고 '핵심 동작'은 다를 때(예: 템플릿 메서드 패턴, 추상 기계, 추상 동물 등).
- 객체의 ‘공통 동작과 상태 미리 구현 + 부분 확장 강제'가 필요할 때.
3. 인터페이스(Interface)
장점
- 다중 구현 가능: 다중 인터페이스 구현이 되어 다양한 역할 조합이 자유롭습니다.
- 유연한 설계: 객체의 행동(기능)이 필요에 따라 유연하게 확장 가능.
- 약한 결합: 상속 관계가 없기 때문에 코드 유연성·재사용성이 높고 테스트, 교체가 쉽습니다.
- 표준 규약: 콜백 구조, API 표준화에 용이해 타 시스템과의 통합이 훨씬 쉽습니다.
단점
- 코드 중복: 모든 구현 클래스에 동일한 코드를 반복 구현해야 할 수 있습니다(일부 기본 구현은 default 메서드로 가능).
- 상태/생성자 불가: 상태(필드), 생성자 등 객체의 기본 속성을 정의할 수 없습니다(상수만 가질 수 있음).
쓰임새
- 대규모 시스템, API, 프레임워크 등에서 다양한 구현 객체가 동일한 메시지를 송수신해야 할 때.
- 다중 역할/기능(콜렉션, 동작, 전략 등)이 필요할 때.
4. 객체지향 설계 원칙에 따른 선택 기준
| 설계 상황 | 추천 개념 | 이유와 설명 |
|---|---|---|
| 'is-a' 관계로 완전한 확장, 코드 재사용 | 슈퍼 클래스/추상 클래스 | 계층 구조 설계에 적합, 공통 동작·상태 물려주기, 기본 기능 추가 확장 용이 |
| 공통 행위 규약(동작만 강제), 다중 역할 | 인터페이스 | 다양한 행동·기능 조합 필수, 다중 구현으로 유연·확장성 갖춤, 약한 결합으로 유지보수 쉬움 |
| 공통 동작 기본 로직+핵심부분만 다양 | 추상 클래스 | 템플릿 메서드 패턴 등, 공통 기능 제공+구현 강제 구조 |
| 여러 역할 동시 수행(예: 개가 동물이면서도 Swimmable) | 인터페이스+상속 혼용 | 다양한 역할 합성 가능, 다양한 시스템과의 통합에 적합 |
| 상태·데이터 반드시 필요(필드/생성자) | 슈퍼/추상 클래스 | 필드, 생성자 로직 등 구현 및 확장 필요 |
결론
- 상속은 계층 구조, 코드 재사용, 공통 기능 확장(JAVA는 단일만 가능, 강한 결합 주의)
- 추상 클래스는 공통 구현+확장 강제(부분 구현 제공, 단일 상속)
- 인터페이스는 다중 구현, 규약화, 유연성, 약한 결합이 필요할 때
⇒ 세밀한 역할 분리와 유연한 설계가 가능하도록 목적에 따라 올바르게 선택하는 것이 객체지향 설계의 핵심입니다.
현실적인 사례에 각 개념(슈퍼 클래스, 추상 클래스, 인터페이스)을 어떻게 적용하는지 구체적인 상황별 예제와 시나리오를 통해 설명하겠습니다.
1. 슈퍼 클래스 (Super Class)
예제 시나리오: 동물원 프로그램
- 동물원에는 여러 종류의 동물이 있다. 모든 동물은 이름, 나이, 먹기(eat) 기능을 가진다.
// 슈퍼 클래스 정의
class Animal {
String name;
int age;
void eat() {
System.out.println(name + "가 먹는다.");
}
}
// 하위 클래스
class Lion extends Animal {
void roar() {
System.out.println(name + "가 으르렁!");
}
}
// 사용
Lion leo = new Lion();
leo.name = "Leo";
leo.eat(); // Leo가 먹는다.
leo.roar(); // Leo가 으르렁!
상황: 여러 동물의 공통 속성과 동작을 코드 중복 없이 상속으로 재사용.
2. 추상 클래스 (Abstract Class)
예제 시나리오: 결제 시스템의 다양한 결제수단
- 모든 결제수단은 거래내역 조회 기능은 동일하지만, 결제 방법은 각기 다르다.
abstract class PaymentMethod {
void showHistory() { System.out.println("거래내역 조회"); }
abstract boolean pay(int amount); // 공통 인터페이스, 구현은 각 방식별 다름
}
class CreditCard extends PaymentMethod {
boolean pay(int amount) {
System.out.println("신용카드 결제 " + amount);
return true;
}
}
class BankTransfer extends PaymentMethod {
boolean pay(int amount) {
System.out.println("계좌이체 결제 " + amount);
return true;
}
}
// 사용
PaymentMethod pm = new CreditCard();
pm.showHistory();
pm.pay(10000);
상황: 결제 방식마다 차별화된 동작이 필요하지만, 공통 책임(내역 조회)은 상위에서 구현.
3. 인터페이스 (Interface)
예제 시나리오: 스마트홈 제어 시스템
- 가전제품마다 전원을 켜는 기능을 공통으로 가져와야 함(이외 기능은 다양).
- 조명은 밝기조절, TV는 채널변경 등 각자 추가 동작이 있음.
interface PowerSwitchable {
void turnOn();
void turnOff();
}
class Light implements PowerSwitchable {
public void turnOn() { System.out.println("조명 켜기"); }
public void turnOff() { System.out.println("조명 끄기"); }
}
class TV implements PowerSwitchable {
public void turnOn() { System.out.println("TV 켜기"); }
public void turnOff() { System.out.println("TV 끄기"); }
}
PowerSwitchable device = new Light();
device.turnOn(); // 조명 켜기
device = new TV();
device.turnOff(); // TV 끄기
상황: 각 클래스가 여러 인터페이스를 동시에 구현할 수 있고, 다양한 객체에 일관된 메시지 전송.
확장: 나중에 AirConditioner 등 추가 기기도 인터페이스만 구현하면 제어 가능.
4. 복합적 예제: 다중 역할, 역할 분리
예시 - 동물이면서 수영도 하는 개
class Animal { /* ... */ }
interface Swimmable { void swim(); }
class Dog extends Animal implements Swimmable {
public void swim() { System.out.println("개가 수영한다"); }
}
- 상황: 코드 재사용(동물 특성) + 행동 규약(수영 역할) 동시 적용.
설계상 언제 무엇을 선택할까?
- 상속으로 계층적(‘is-a’) 관계, 코드 재사용이 최우선이면 ⇒ 슈퍼 클래스/추상 클래스
- 기능적 역할(‘can-do’), 시스템 확장과 약한 결합이 목표라면 ⇒ 인터페이스
- 공통 기본로직+특정 동작만 다양화 ⇒ 추상 클래스
- 여러 역할(비슷하지 않은 기능) 동시에 필요 ⇒ 인터페이스 다중 구현
references